﻿#region Using Statements
using System;
using GameBase.Graphics;
using GameBase.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace StarFox.ScreenComponents
{
    /// <summary>
    /// Implementation of a skybox for landscape drawing.
    /// </summary>
    public class SkyBox : ScreenComponent3D, IDisposable
    {
        #region SkyBox Fields

        Effect skbEffect;

        // vertices
        VertexPosition[] verts;
        short[] indexs;
        VertexBuffer vertexBuffer;

        #endregion

        #region Matrix Fields

        Matrix world = Matrix.Identity;
        Matrix rotationMatrix = Matrix.Identity;
        Matrix projection;

        // projection parameters
        float fov = MathHelper.PiOver2;
        float aspectRatio = 1.0f;
        float nearField = 1.0f;
        float farField = 10.0f;

        #endregion

        #region Properties

        /// <summary>
        /// Sets the rotation angle (in degrees) of the skybox.
        /// </summary>
        public float Rotation
        {
            set
            {
                float rotationAngle = Convert.ToSingle(Math.PI) * value / 180.0f;
                rotationMatrix = Matrix.CreateRotationY(rotationAngle);
            }
        }

        /// <summary>
        /// Sets the field of view (in radians) used to draw the skybox.
        /// </summary>
        public float FieldOfView
        {
            set
            {
                fov = value;
                projection = Matrix.CreatePerspectiveFieldOfView(fov, aspectRatio, nearField, farField);
            }
        }

        /// <summary>
        /// Sets the aspect ratio used to draw the skybox.
        /// </summary>
        public float AspectRatio
        {
            set
            {
                aspectRatio = value;
                projection = Matrix.CreatePerspectiveFieldOfView(fov, aspectRatio, nearField, farField);
            }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="screen">The Screen that this ScreenComponent3D is associated to.</param>
        public SkyBox(Screen screen)
            : base(screen)
        {
            TextureCube texture = Screen.Content.Load<TextureCube>(ContentSettings.GetAsset("SKYBOX_TEXTURE"));
            skbEffect = Screen.Content.Load<Effect>(ContentSettings.GetAsset("SKYBOX_EFFECT"));
            skbEffect.Parameters["SkyBoxTex"].SetValue(texture);

            projection = Matrix.CreatePerspectiveFieldOfView(fov, aspectRatio, nearField, farField);

            CreateSkyBox();
        }

        /// <summary>
        /// Initializes the cube used to draw the skybox.
        /// </summary>
        private void CreateSkyBox()
        {
            #region Cube Definition

            verts = new VertexPosition[8];
            int i = 0;

            // front side
            verts[i++] = new VertexPosition(new Vector3(-1, -1, 1));
            verts[i++] = new VertexPosition(new Vector3(1, -1, 1));
            verts[i++] = new VertexPosition(new Vector3(-1, 1, 1));
            verts[i++] = new VertexPosition(new Vector3(1, 1, 1));
            // back side
            verts[i++] = new VertexPosition(new Vector3(-1, -1, -1));
            verts[i++] = new VertexPosition(new Vector3(1, -1, -1));
            verts[i++] = new VertexPosition(new Vector3(-1, 1, -1));
            verts[i++] = new VertexPosition(new Vector3(1, 1, -1));

            indexs = new short[] { 0, 2, 3, 0, 3, 1,    // front
                                   5, 7, 6, 5, 6, 4,    // back
                                   2, 6, 7, 2, 7, 3,    // top
                                   1, 5, 4, 1, 4, 0,    // bottom
                                   1, 3, 7, 1, 7, 5,    // right
                                   4, 6, 2, 4, 2, 0     // left
            };

            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPosition), verts.Length, BufferUsage.None);
            vertexBuffer.SetData(verts);
            GraphicsDevice.SetVertexBuffer(vertexBuffer);

            #endregion
        }

        #endregion

        #region Dispose

        // dispose flag
        bool isDisposed = false;

        /// <summary>
        /// Immediately releases all resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Allows an object to try to free resources and perform other cleanup operations before it is reclaimed by garbage collection.
        /// </summary>
        ~SkyBox()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by this object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    if (vertexBuffer != null) { vertexBuffer.Dispose(); vertexBuffer = null; }
                }

                isDisposed = true;
            }
        }

        #endregion

        #region Update

        /// <summary>
        /// Called when the ScreenComponent3D needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to this method.</param>
        public override void Update(GameTime gameTime)
        {
            // rotation of the skybox
            world *= rotationMatrix;

            // adjust view matrix
            Matrix auxView = Matrix.Invert(Camera.View);
            auxView.Translation = Vector3.Zero;
            auxView = Matrix.Invert(auxView);

            // effect configuration
            skbEffect.Parameters["WorldViewProjection"].SetValue(world * auxView * projection);
        }

        #endregion

        #region Draw

        /// <summary>
        /// Called when the ScreenComponent3D needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to this method.</param>
        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.None;
            GraphicsDevice.RasterizerState = RasterizerState.CullClockwise;

            foreach (EffectPass pass in skbEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawUserIndexedPrimitives<VertexPosition>(PrimitiveType.TriangleList, verts, 0, 8, indexs, 0, 12);
            }
        }

        #endregion
    }
}
